﻿
namespace Octopus.VMDeployer

open System
open System.Diagnostics
open System.Configuration.Install
open System.Management


module private Win32_Service =
  let private jobCompleted (outParams:ManagementBaseObject) scope =
    let jobPath = outParams.["Job"] :?> string
    use job = new ManagementObject(scope, new ManagementPath(jobPath), null)
    job.Get() // Try to get storage job information
    while (job.["JobState"] :?> uint16) = 3us (*uint16(JobState.Starting)*) || (job.["JobState"] :?> uint16) = 4us (*uint16(JobState.Running)*) do
      System.Threading.Thread.Sleep(1000)
      job.Get()
    // Figure out if job failed
    let jobState = job.["JobState"] :?> uint16
    if jobState <> 7us then //uint16(JobState.Completed)
      let jobErrorCode = job.["ErrorCode"] :?> uint16
      failwithf "Error Code: %d, ErrorDescription: %s" jobErrorCode (job.["ErrorDescription"].ToString())
    else
      true

  let createService (name:string) (displayName:string) (pathName:string) (serviceDependencies:string array) =
    let scope = new ManagementScope(@"root\cimv2", null)
    let imageService = new ManagementClass(scope, new ManagementPath("Win32_Service"), null)
    use inParams = imageService.GetMethodParameters("Create")
    inParams.["Name"] <- name
    inParams.["DisplayName"] <- displayName
    inParams.["PathName"] <- pathName
    inParams.["ServiceType"] <- 0x10us   // Own Process
    inParams.["ErrorControl"] <- 0us     // User is not notified
    inParams.["StartMode"] <- "Automatic"
    inParams.["DesktopInteract"] <- false
    inParams.["StartName"] <- "LocalSystem"    // LocalSystem
    inParams.["StartPassword"] <- ""
    inParams.["ServiceDependencies"] <- serviceDependencies

    use outParams = imageService.InvokeMethod("Create", inParams, null)

    let code = outParams.["ReturnValue"] :?> uint32
    if code = 4096u then //uint32(Utility.ReturnCode.Started)
      if jobCompleted outParams scope then ()
      else failwith "Unable to create the service"
    else if code = 0u then () //uint32(Utility.ReturnCode.Completed)
    else failwithf "Unable to create the service. Error %d" code

  let startService (name:string) =
    let objPath = sprintf "Win32_Service.Name='%s'" name
    use service = new ManagementObject(new ManagementPath(objPath))
    service.InvokeMethod("StartService", null, null) |> ignore

  let stopService (name:string) =
    let objPath = sprintf "Win32_Service.Name='%s'" name
    use service = new ManagementObject(new ManagementPath(objPath))
    service.InvokeMethod("StopService", null, null) |> ignore
    
  let interrogateService (name:string) =
    let objPath = sprintf "Win32_Service.Name='%s'" name
    use service = new ManagementObject(new ManagementPath(objPath))
    try
      service.InvokeMethod("InterrogateService", null, null) |> ignore
      true
    with _ -> false

  let deleteService (name:string) =
    let objPath = sprintf "Win32_Service.Name='%s'" name
    use service = new ManagementObject(new ManagementPath(objPath))
    service.InvokeMethod("Delete", null, null) |> ignore



[<System.ComponentModel.RunInstaller(true)>]
type DeployerInstaller =
  inherit System.Configuration.Install.Installer

  member private x.serviceName = "OctopusVMDeployer"
  member private x.serviceDisplayName = "Octopus VM Deployer"
  member private x.serviceBinName = "DeployerService.exe"

  /// <summary>
  /// Target dir of the project. 
  /// It Is not null only if the TargetDir custom data is specified.
  /// </summary>
  member private x.TargetDir
      with get() =
        if not(x.Context.Parameters.ContainsKey("TargetDir")) then null
        else
          let tmp = let t = x.Context.Parameters.["TargetDir"] in if t.EndsWith(@"\") then t else (t + @"\")
          if tmp.StartsWith(@"\") && not( tmp.StartsWith(@"\\") ) then (@"\" + tmp)
          else tmp


  override x.Install savedState =
      base.Install(savedState)
      // ---
      let binPath = x.TargetDir + x.serviceBinName
      Win32_Service.createService x.serviceName x.serviceDisplayName binPath null
      Win32_Service.startService x.serviceName


  override x.OnBeforeUninstall savedState =
      base.OnBeforeUninstall(savedState)
      // ---
      try
        Win32_Service.stopService x.serviceName
        Win32_Service.deleteService x.serviceName
      with _ -> ()

  new () = {}